﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
//using System.Threading.Tasks;
using CIAWorldMap;
using OSMLib;
using Route;
using WGS84;

namespace LaoMap
{
    delegate void DelegateToggleWay(int id);

    public static class RouteManager
    {
        const string FILE_NAME = @"c:\temp\LaosRoutes.bin";
        private static string _routenameLoaded = "";
        private static Dictionary<int, Trek> _selection;

        public static Dictionary<int, Trek> GetSelection
        {
            get { return _selection; }
        }

        static RouteManager()
        {
            _selection = new Dictionary<int, Trek>();
        }

        public static void ToggleWay(int id)
        {
            if (_selection.ContainsKey(id))
            {
                _selection.Remove(id);
            }
            else
            {
                _selection.Add(id, new Trek(id, -1, -1, WayDirection.Positive));
            }
        }

        public static void LoadCache()
        {
            _routenameLoaded = "";
            _selection = new Dictionary<int, Trek>();

            Cache cache = Cache.Load(FILE_NAME);
            if (cache.Legs.Count == 0)
                return;

            _routenameLoaded = cache.Legs[cache.Legs.Count - 1].Description;
            // most recent
            foreach (var t in cache.Legs[cache.Legs.Count - 1].Treks)
            {
                _selection.Add(t.WayID, t);
            }
        }

        public static string GetLoadedRouteName()
        {
            return _routenameLoaded;
        }

        private static Leg BuildOrderedTreks()
        {
            if (Osm.Ways.Count == 0) // osm not loaded
                return null;

            if (_selection.Count == 0)
            {
                return null;
            }

            var l = new Leg();

            if (_selection.Count == 1)
            {
                foreach (var t in _selection.Values)
                {
                    int count = ((OsmWay)Osm.Ways[t.WayID]).Nodes.Count;
                    l.AddTrek(new Trek(t.WayID, 0, count - 1, WayDirection.Positive));
                }
                return l;
            }

            // find the middle of the leg
            double meanLat = 0;
            double meanLong = 0;
            int count1 = 0;

            foreach (var t in _selection.Values)
            {
                List<IOsmNode> nodes = ((OsmWay)Osm.Ways[t.WayID]).Nodes;

                foreach (OsmNode n in nodes)
                {
                    meanLat += n._wgs.Latitude;
                    meanLong += n._wgs.Longitude;
                    count1++;
                }
            }

            meanLat = meanLat / count1;
            meanLong = meanLong / count1;

            var midLeg = new Wgs84Coordinate(meanLat, meanLong);

            OsmNode firstNode = null;
            OsmNode lastNode = null;

            var unsortedWayIds = new List<int>();
            foreach (var t in _selection.Values)
            {
                if (l.Treks.Count == 0)
                {
                    // first node will be the furthest away from (arbirtarily) the mid point.

                    var first = (OsmNode)((OsmWay)Osm.Ways[t.WayID]).Nodes[0];

                    int count = ((OsmWay)Osm.Ways[t.WayID]).Nodes.Count;
                    var last = (OsmNode)((OsmWay)Osm.Ways[t.WayID]).Nodes[count - 1];

                    double firstDistance = first._wgs.Distance(midLeg);
                    double lastDistance = last._wgs.Distance(midLeg);
                    if (firstDistance > lastDistance) // looking for furthest away from mid point
                    {
                        firstNode = first;
                        lastNode = last;
                        l.AddTrek(new Trek(t.WayID, 0, count - 1, WayDirection.Positive));
                    }
                    else
                    {
                        firstNode = last;
                        lastNode = first;
                        l.AddTrek(new Trek(t.WayID, count - 1, 0, WayDirection.Negative));
                    }
                    // so we have the first trek in the leg all sorted... but the remaining ways may not be correctly sorted...
                }
                else
                {
                    unsortedWayIds.Add(t.WayID);
                }
            }

            unsortedWayIds.Sort(new MyComparer(firstNode));

            if (lastNode == null)
            {
                throw new ApplicationException("lastNode null");
            }

            // way list sorted - but directions may change...

            foreach (int wayID in unsortedWayIds)
            {
                var first = (OsmNode)((OsmWay)Osm.Ways[wayID]).Nodes[0];
                int count = ((OsmWay)Osm.Ways[wayID]).Nodes.Count;
                var last = (OsmNode)((OsmWay)Osm.Ways[wayID]).Nodes[count - 1];

                double firstDistance = first._wgs.Distance(lastNode._wgs);
                double lastDistance = last._wgs.Distance(lastNode._wgs);

                if (firstDistance < lastDistance)// looking for nearest to last point
                {
                    l.AddTrek(new Trek(wayID, 0, count - 1, WayDirection.Positive));
                    lastNode = last;
                }
                else
                {
                    l.AddTrek(new Trek(wayID, count - 1, 0, WayDirection.Negative));
                    lastNode = first;
                }
            }

            return l;
        }

        public static void SaveCache(string routeName)
        {
            Leg leg = BuildOrderedTreks();
            if (leg == null)
                return;

            leg.Description = routeName;

            Cache cache = Cache.Load(FILE_NAME);
            cache.Legs.Add(leg);
            cache.Save(FILE_NAME);
        }

        public static void ClearCurrent()
        {
            _selection.Clear();
        }

        public static void AnimateCurrent(Dictionary<string, Place> places)
        {
            //Task.Factory.StartNew((() => Life(places)), TaskCreationOptions.LongRunning);
        }

        private static void Lifeold()
        {
            var nodes = (from t in _selection.Values from OsmNode n in ((OsmWay)Osm.Ways[t.WayID]).Nodes select n).ToList();

            if (nodes.Count == 0)
            {
                Debug.WriteLine("Nowhere to go!");
                return;
            }
            int step = 0;

            var node = nodes[0];
            var destination = nodes[nodes.Count - 1];

            double totalDistance = 0;

            Debug.WriteLine("Starting");
            while (true)
            {
                step++;
                if (step == nodes.Count)
                    break;
                OsmNode nodeNext = nodes[step];
                // when will I do something noteworthy?
                double firstDistance = node._wgs.Distance(nodeNext._wgs);
                // travel at 50 kph 
                double durationMs = firstDistance * 60 * 60 * 1000 / 50;
                Thread.Sleep((int)durationMs);
                totalDistance += firstDistance;
                double howFarToGo = nodeNext._wgs.Distance(destination._wgs);
                var bearing = nodeNext._wgs.Bearing(destination._wgs);

                Debug.WriteLine("" + _routenameLoaded + " Reached node " + step + ". We've travelled " + totalDistance + " km. " + howFarToGo + " km to go, on bearing " + bearing.ToDegrees());
                node = nodeNext;
            }
            Debug.WriteLine("Completed");
        }

        private static void Life(Dictionary<string, Place> places1)
        {
            var nodes1 = (from t in _selection.Values from OsmNode n in ((OsmWay) Osm.Ways[t.WayID]).Nodes select n).ToList();

            List<Place> places = GetPlacesOnWay(nodes1, places1);

            if (places.Count == 0)
            {
                Debug.WriteLine("Nowhere to go!");
                return;
            }
            int step = 0;

            var placeStart= places[0];
            var destination = places[places.Count - 1];

            double totalDistance = 0;

            Debug.WriteLine("Starting");
            while (true)
            {
                step++;
                if (step == places.Count)
                    break;
                Place nodeNext = places[step];
                // when will I do something noteworthy?
                double firstDistance = placeStart._Wgs84Coordinate.Distance(nodeNext._Wgs84Coordinate);
                // travel at 50 kph 
                double durationMs = firstDistance * 60 * 60 * 1000 / 50;
               // Thread.Sleep((int)durationMs);
                totalDistance += firstDistance;
                double howFarToGo = nodeNext._Wgs84Coordinate.Distance(destination._Wgs84Coordinate);
                var bearing = placeStart._Wgs84Coordinate.Bearing(nodeNext._Wgs84Coordinate);

                Debug.WriteLine("" + _routenameLoaded + " Reached node " + step + 
                    ". We've travelled " + totalDistance + " km. " + howFarToGo + " km to go, on bearing " + bearing.ToDegrees());
                foreach (var placeName in nodeNext._names)
                {
                    Debug.WriteLine(placeName);
                }
                
                placeStart = nodeNext;
            }
            Debug.WriteLine("Completed");
        }

        private static List<Place> GetPlacesOnWay(List<OsmNode> nodes, Dictionary<string, Place> places1)
        {
            if (nodes.Count <= 1)
            {
                return new List<Place>();
            }

            // build bounding box that will surround our route
        
            BoundingBox bb = new BoundingBox();
            bb.BuildAndInflateBB(nodes);

            List<Place> potentialPlaces = new List<Place>();
            foreach (var place in places1.Values)
            {
                if (bb.WayVisible(place))
                {
                    potentialPlaces.Add(place);
                }
            }

            // get closest point to each place

            List<PlaceDistance> distances = new List<PlaceDistance>();

            foreach (var potential in potentialPlaces)
            {
                PlaceDistance pd = new PlaceDistance();
                pd._place = potential;

                double totalDistanceOnRoute = 0;

                for (int x = 0; x < nodes.Count - 1; x++)
                {
                    double distanceToPlaceFromNode = nodes[x]._wgs.Distance(potential._Wgs84Coordinate);

                    if (distanceToPlaceFromNode <= 2.0)
                    {
                        if (distanceToPlaceFromNode < pd._distanceToPlace)
                        {
                            pd._distanceToPlace = distanceToPlaceFromNode;
                            pd._distanceAlongWay = totalDistanceOnRoute;
                            pd._place = potential;
                        }
                    }
                    double distanceOnStep = nodes[x]._wgs.Distance(nodes[x+1]._wgs);
                    totalDistanceOnRoute += distanceOnStep;
                }

                if (pd._distanceToPlace < 2.0)
                {
                    distances.Add(pd);
                }
            }

            distances.Sort(new MyDistanceFromStartSorter());

            List<Place> ret = new List<Place>();
            foreach (var pd in distances)
            {
                ret.Add(pd._place);
            }
            return ret;
        }


 
      
    }

    internal class MyDistanceFromStartSorter  : IComparer<PlaceDistance>
    {
        public int Compare(PlaceDistance x, PlaceDistance y)
        {
            return (int)((x._distanceAlongWay - y._distanceAlongWay)* 1000);
        }
    }

    class PlaceDistance
    {
        public double _distanceToPlace = double.PositiveInfinity;
        public double _distanceAlongWay = 0;
        public Place _place;
    }

    class BoundingBox
    {
        double _northLimit = double.NegativeInfinity;
        double _southLimit = double.PositiveInfinity;
        double _eastLimit = double.NegativeInfinity;
        double _westLimit = double.PositiveInfinity;

        public void BuildAndInflateBB(List<OsmNode> nodes)
        {
            foreach (OsmNode node in nodes)
            {
                _northLimit = Math.Max(_northLimit, node._wgs.Latitude);
                _southLimit = Math.Min(_southLimit, node._wgs.Latitude);
                _eastLimit = Math.Max(_eastLimit, node._wgs.Longitude);
                _westLimit = Math.Min(_westLimit, node._wgs.Longitude);
            }

            // expand box slightly so we get 1km distant places
            _northLimit += 0.02;
            _southLimit -= 0.02;
            _eastLimit += 0.02;
            _westLimit -= 0.02;
        }

        public bool WayVisible(Place p)
        {
            if (_eastLimit < p._Wgs84Coordinate.Longitude)
                return false;

            if (_southLimit > p._Wgs84Coordinate.Latitude)
                return false;

            if (_westLimit > p._Wgs84Coordinate.Longitude)
                return false;

            if (_northLimit < p._Wgs84Coordinate.Latitude)
                return false;

            return true;
        }
    }

}
